View source with raw comments or as raw
    1/*  Part of ClioPatria SeRQL and SPARQL server
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@cs.vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (C): 2010, University of Amsterdam,
    7		   VU University Amsterdam
    8
    9    This program is free software; you can redistribute it and/or
   10    modify it under the terms of the GNU General Public License
   11    as published by the Free Software Foundation; either version 2
   12    of the License, or (at your option) any later version.
   13
   14    This program is distributed in the hope that it will be useful,
   15    but WITHOUT ANY WARRANTY; without even the implied warranty of
   16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   17    GNU General Public License for more details.
   18
   19    You should have received a copy of the GNU General Public
   20    License along with this library; if not, write to the Free Software
   21    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   22
   23    As a special exception, if you link this library with other files,
   24    compiled with a Free Software compiler, to produce an executable, this
   25    library does not by itself cause the resulting executable to be covered
   26    by the GNU General Public License. This exception does not however
   27    invalidate any other reasons why the executable file might be covered by
   28    the GNU General Public License.
   29*/
   30
   31:- module(cp_simple_search,
   32	  [ simple_search_form//0,
   33	    simple_search_form//1,	% +Options
   34	    search_filter/2
   35	  ]).   36:- use_module(library(http/http_json)).   37:- use_module(library(http/http_parameters)).   38:- use_module(library(http/http_path)).   39:- use_module(library(http/http_dispatch)).   40:- use_module(library(http/http_wrapper)).   41:- use_module(library(http/html_write)).   42:- use_module(library(http/html_head)).   43
   44:- use_module(library(semweb/rdf_db)).   45:- use_module(library(semweb/rdfs)).   46:- use_module(library(semweb/rdf_label)).   47
   48:- use_module(library(option)).   49:- use_module(components(basics)).   50
   51
   52:- http_handler(api(ac_find_literal), ac_find_literal, []).

Simple literal search

*/

 simple_search_form// is det
 simple_search_form(+Options)// is det
Provide a search form to find literals in the database. Options processed:
id(ID)
Identifier-base for the search-box. The actual box is called ID_<complete>
filter(+Filter)
Restrict results to resources that satisfy filter. Filtering is implemented by search_filter/2.
select_handler(+HandlerID)
ID of the handler called if the user selects a completion. The handler is called with q=<Selected>
submit_handler(+HandlerID)
ID of the handler called if the user submits using the button. The handler is called with q=<Typed>
label(Label)
Label of the search-button. Default is Search.
value(Value)
Initial value of the search-box
width(Width)
Width of the input box (default is 25em). Must be a CSS width.
   83simple_search_form -->
   84	simple_search_form([]).
   85
   86simple_search_form(Options) -->
   87	{ option(label(Label), Options, 'Search'),
   88	  option(submit_handler(Search), Options, search)
   89	},
   90	html(form([ id(search_form),
   91		    action(location_by_id(Search))
   92		  ],
   93		  [ div([ \search_box([ name(q) | Options ]),
   94			  \filter(Options),
   95			  \select_handler(Options),
   96			  input([ type(submit),
   97				  value(Label)
   98				])
   99			])
  100		  ])).
  101
  102filter(Options) -->
  103	{ option(filter(Filter), Options), !,
  104	  term_to_atom(Filter, FilterAtom)
  105	},
  106	hidden(filter, FilterAtom).
  107filter(_) --> [].
  108
  109select_handler(Options) -->
  110	{ option(select_handler(Handler), Options) }, !,
  111	hidden(handler, Handler).
  112select_handler(_) --> [].
  113
  114
  115max_results_displayed(100).
  116
  117search_box(Options) -->
  118	{ max_results_displayed(Max)
  119	},
  120	autocomplete(ac_find_literal,
  121		     [ query_delay(0.2),
  122		       auto_highlight(false),
  123		       max_results_displayed(Max),
  124		       width('30ex')
  125		     | Options
  126		     ]).
 autocomplete(+HandlerID, +Options)// is det
Insert a YUI autocomplete widget that obtains its alternatives from HandlerID. The following Options are supported:
width(+Width)
Specify the width of the box. Width must satisfy the CSS length syntax.
query_delay(+Seconds)
Wait until no more keys are typed for Seconds before sending the query to the server.
  141autocomplete(Handler, Options) -->
  142	{ option(id(ID), Options, ac_find_literal),
  143	  atom_concat(ID, '_complete', CompleteID),
  144	  atom_concat(ID, '_input', InputID),
  145	  atom_concat(ID, '_container', ContainerID),
  146	  select_option(width(Width), Options, Options1, '25em'),
  147	  select_option(name(Name), Options1, Options2, predicate),
  148	  select_option(value(PValue), Options2, Options3, ''),
  149	  expand_value(PValue, Value)
  150	},
  151	html([ \html_requires(yui('autocomplete/autocomplete.js')),
  152	       \html_requires(yui('autocomplete/assets/skins/sam/autocomplete.css')),
  153	       div([ id(CompleteID),
  154		     class(ac_input)
  155		   ],
  156		   [ input([ id(InputID),
  157			     name(Name),
  158			     value(Value),
  159			     type(text)
  160			   ]),
  161		     div(id(ContainerID), [])
  162		   ]),
  163	       style(type('text/css'),
  164		     [ '#', CompleteID, '\n',
  165		       '{ width:~w; padding-bottom:0em; display:inline-block; vertical-align:top}'-[Width]
  166		     ]),
  167	       \autocomplete_script(Handler, InputID, ContainerID, Options3)
  168	     ]).
 expand_value(ValueIn, Value)
Allow for e.g., p(q) to use the value from the HTTP-parameter q.
  175expand_value(p(Name), Value) :- !,
  176	(   http_current_request(Request),
  177	    memberchk(search(Search), Request),
  178	    memberchk(Name=PValue, Search)
  179	->  Value = PValue
  180	;   Value = ''
  181	).
  182expand_value(Value, Value).
  183
  184
  185highlight -->
  186	html(script(type('text/javascript'),
  187\[
  188  'function highlighMatches(str, query, cls)\n',
  189  '{ var pat = new RegExp(query, "gi");
  190     var sa = str.split(pat);
  191     var ma = str.match(pat);
  192     var i;
  193     var out = sa[0];\n',
  194
  195  '  if ( !ma )
  196     { return str;
  197     }\n',
  198
  199  '  for(i=0; i<ma.length; )
  200     { out += "<span class=\'"+cls+"\'>"+ma[i++]+"</span>";
  201       out += sa[i];
  202     }\n',
  203
  204  'return out;
  205   }\n'
  206 ])).
  207
  208autocomplete_script(HandlerID, Input, Container, Options) -->
  209	{ http_link_to_id(HandlerID, [], Path),
  210	  option(filter(Filter), Options, true),
  211	  term_to_atom(Filter, FilterAtom),
  212	  uri_query_components(QS, [filter(FilterAtom)])
  213	},
  214	highlight,
  215	html(script(type('text/javascript'), \[
  216'{ \n',
  217'  var oDS = new YAHOO.util.XHRDataSource("~w");\n'-[Path],
  218'  oDS.responseType = YAHOO.util.XHRDataSource.TYPE_JSON;\n',
  219'  oDS.responseSchema = { resultsList:"results",
  220			  fields:["label","count","href"]
  221			};\n',
  222'  oDS.maxCacheEntries = 5;\n',
  223'  var oAC = new YAHOO.widget.AutoComplete("~w", "~w", oDS);\n'-[Input, Container],
  224'  oAC.resultTypeList = false;\n',
  225'  oAC.formatResult = function(oResultData, sQuery, sResultMatch) {
  226     var sLabel = highlighMatches(oResultData.label, sQuery, "acmatch");
  227     if ( oResultData.count > 1 ) {
  228       sLabel += " <span class=\\"account\\">("+oResultData.count+")</span>";
  229     }
  230     return sLabel;
  231   };\n',
  232'  oAC.itemSelectEvent.subscribe(function(sType, aArgs) {
  233     var oData = aArgs[2];
  234     window.location.href = oData.href;
  235   });\n',
  236'  oAC.generateRequest = function(sQuery) {
  237        return "?~w&query=" + sQuery ;
  238    };\n'-[QS],
  239    \ac_options(Options),
  240'}\n'
  241					     ])).
  242ac_options([]) -->
  243	[].
  244ac_options([H|T]) -->
  245	ac_option(H),
  246	ac_options(T).
  247
  248ac_option(query_delay(Time)) --> !,
  249	html([ '  oAC.queryDelay = ~w;\n'-[Time] ]).
  250ac_option(auto_highlight(Bool)) --> !,
  251	html([ '  oAC.autoHighlight = ~w;\n'-[Bool] ]).
  252ac_option(max_results_displayed(Max)) --> !,
  253	html([ '  oAC.maxResultsDisplayed = ~w;\n'-[Max] ]).
  254ac_option(_) --> [].
 ac_find_literal(+Request)
Perform autocompletion for literals and resources. The reply is a JSON object that is normally used in a YUI autocomplete widget.
  262ac_find_literal(Request) :-
  263	max_results_displayed(DefMax),
  264	http_parameters(Request,
  265			[ query(Query,
  266				[ description('Prefix for literals to find')
  267				]),
  268			  filter(FilterAtom,
  269				 [ optional(true),
  270				   description('Filter on raw matches (a Prolog term)')
  271				 ]),
  272			  handler(Handler,
  273				  [ default(list_triples_with_literal),
  274				    description('Callback handler on selection')
  275				  ]),
  276			  maxResultsDisplayed(Max,
  277					      [ integer, default(DefMax),
  278						description('Maximum number of results displayed')
  279					      ])
  280			]),
  281	(   var(FilterAtom)
  282	->  Filter = true
  283	;   atom_to_term(FilterAtom, Filter0, []),
  284	    rdf_global_term(Filter0, Filter)
  285	),
  286	autocompletions(Query, Filter, Handler, Max, Count, Completions),
  287	reply_json(json([ query = json([ count=Count
  288				       ]),
  289			  results = Completions
  290			])).
  291
  292autocompletions(Query, Filter, Handler, Max, Count, Completions)  :-
  293	autocompletions(prefix(label), Query, Filter,
  294			Handler, Max, BNC, ByName),
  295	(   BNC > Max
  296	->  Completions = ByName,
  297	    Count = BNC
  298	;   TMax is Max-BNC,
  299	    autocompletions(prefix(other), Query, Filter,
  300			    Handler, TMax, BTC, ByToken),
  301	    append(ByName, ByToken, Completions),
  302	    Count is BNC+BTC
  303	).
  304
  305autocompletions(How, Query, Filter, Handler, Max, Count, Completions) :-
  306	ac_objects(How, Query, Filter, Completions0),
  307	length(Completions0, Count),
  308	first_n(Max, Completions0, Completions1),
  309	maplist(obj_result(Handler), Completions1, Completions).
  310
  311obj_result(Handler, Text-Count,
  312	   json([ label=Text,
  313		  count=Count,
  314		  href=Href
  315		])) :-
  316	object_href(Handler, Text, Href).
  317
  318object_href(Handler, Text, Link) :- !,
  319	http_link_to_id(Handler, [ q=Text ], Link).
  320
  321first_n(0, _, []) :- !.
  322first_n(_, [], []) :- !.
  323first_n(N, [H|T0], [H|T]) :-
  324	N2 is N - 1,
  325	first_n(N2, T0, T).
 ac_objects(+How, +Query, +Filter, -Objects)
Arguments:
Objects- is a list of Text-Count pairs
  332ac_objects(How, Query, Filter, Objects) :-
  333	findall(Pair, ac_object(How, Query, Filter, Pair), Pairs),
  334	keysort(Pairs, KSorted),
  335	group_pairs_by_key(KSorted, Grouped),
  336	maplist(hit_count, Grouped, Objects).
  337
  338hit_count(Text-Resources, Text-Count) :-
  339	length(Resources, Count).	% duplicates?
 ac_object(+How, +Query, +Filter, -Object)
  344ac_object(prefix(label), Query, Filter, Text-Resource) :-
  345	ac_candidate(Query, Filter, Resource, P, Literal),
  346	(   label_property(LP),
  347	    rdfs_subproperty_of(P, LP)
  348	->  literal_text(Literal, Text)
  349	).
  350ac_object(prefix(other), Query, Filter, Text-Resource) :-
  351	ac_candidate(Query, Filter, Resource, P, Literal),
  352	(   label_property(LP),
  353	    rdfs_subproperty_of(P, LP)
  354	->  fail
  355	;   literal_text(Literal, Text)
  356	).
  357
  358ac_candidate(Query, Filter, R, P, Literal) :-
  359	(   sub_term(graph(Graph), Filter)
  360	->  rdf(R, P, literal(prefix(Query), Literal), Graph)
  361	;   rdf(R, P, literal(prefix(Query), Literal))
  362	),
  363	search_filter(Filter, R).
 search_filter(+Filter, +Resource) is semidet
True if Filter holds for Resource. Defined filters are:
true
Always true
graph(Graph)
The triple providing the literal must reside in Graph.
  374search_filter(true, _) :- !.
  375search_filter(graph(_), _) :- !.		% already filtered
  376search_filter(Filter, _) :-
  377	domain_error(filter, Filter)