View source with formatted comments or as raw
    1/*  Part of SWISH
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2014-2016, VU University Amsterdam
    7    All rights reserved.
    8
    9    Redistribution and use in source and binary forms, with or without
   10    modification, are permitted provided that the following conditions
   11    are met:
   12
   13    1. Redistributions of source code must retain the above copyright
   14       notice, this list of conditions and the following disclaimer.
   15
   16    2. Redistributions in binary form must reproduce the above copyright
   17       notice, this list of conditions and the following disclaimer in
   18       the documentation and/or other materials provided with the
   19       distribution.
   20
   21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   32    POSSIBILITY OF SUCH DAMAGE.
   33*/
   34
   35:- module(swish_render,
   36	  [ use_rendering/1,		% +Renderer
   37	    use_rendering/2,		% +Renderer, +Options
   38
   39	    register_renderer/2,	% Declare a rendering module
   40	    current_renderer/2		% Name, Comment
   41	  ]).   42:- use_module(library(pengines_io), []).   43:- use_module(library(http/html_write)).   44:- use_module(library(http/term_html)).   45:- use_module(library(option)).   46:- use_module(library(error)).   47
   48:- meta_predicate
   49	register_renderer(:, +),
   50	use_rendering(:),
   51	use_rendering(:, +).   52
   53/** <module> SWISH term-rendering support
   54
   55This module manages rendering answers  using alternative vizualizations.
   56The idea is that a  specific  context   _uses_  zero  or  more rendering
   57modules.  These  rendering  modules   provide    an   alternative   HTML
   58representation for the target term. If  multiple possible renderings are
   59found,  a  =|<div  class="render-multi">|=  element  is  generated  that
   60contains the alternative renderings. The   jQuery  plugin =renderMulti=,
   61defined in =answer.js= adds the  behaviour   to  change rendering to the
   62generated div.
   63
   64The user can import rendering schemes into the current context using the
   65directive below. `Spec` is either an atom   or string, making the system
   66look for render(Spec), or it is a   (single) file specification that can
   67be used for use_module/1.
   68
   69  ==
   70  :- use_rendering(Spec).
   71  ==
   72
   73A rendering module is a  Prolog   module  that  defines the non-terminal
   74term_rendering//3,  which  will  be  called  as  below.  `Term`  is  the
   75(non-var) term that must be rendered, `Vars` is a list of variable names
   76bound to this term and `Options` is a   list of write options that would
   77normally  be  passed  to  write_term/3.  The   grammar  is  executed  by
   78library(http/html_write) and must  generate   compatible  tokens  (which
   79means it must call html//1 to generate HTML tokens).
   80
   81  ==
   82  phrase(Renderer:term_rendering(Term, Vars, Options), Tokens)
   83  ==
   84*/
   85
   86:- multifile user:file_search_path/2.   87
   88user:file_search_path(render, swish('lib/render')).
   89
   90
   91%%	use_rendering(+FileOrID)
   92%
   93%	Register an answer  renderer.   Same  as use_rendering(FileOrID,
   94%	[]).
   95%
   96%	@see use_rendering/2.
   97
   98:- multifile system:term_expansion/2.   99
  100use_rendering(Rendering) :-
  101	use_rendering(Rendering, []).
  102
  103%%	use_rendering(:ID, +Options)
  104%
  105%	Register an answer renderer  with   options.  Options are merged
  106%	with   write-options   and   passed     to    the   non-terminal
  107%	term_rendering//3 defined in the rendering module.
  108
  109use_rendering(Rendering, Options) :-
  110	Rendering = Into:Renderer,
  111	must_be(atom, Renderer),
  112	(   renderer(Renderer, _, _)
  113	->  true
  114	;   existence_error(renderer, Renderer)
  115	),
  116	retractall(Into:'swish renderer'(Renderer, _)),
  117	assertz(Into:'swish renderer'(Renderer, Options)).
  118
  119system:term_expansion((:- use_rendering(Renderer)), Expanded) :-
  120	expand_rendering(Renderer, [], Expanded).
  121system:term_expansion((:- use_rendering(Renderer, Options)), Expanded) :-
  122	expand_rendering(Renderer, Options, Expanded).
  123
  124expand_rendering(Module:Renderer, Options,
  125		 [ (:- discontiguous(Module:'swish renderer'/2)),
  126		   Module:'swish renderer'(Renderer, Options)
  127		 ]) :- !,
  128	must_be(atom, Module),
  129	must_be(atom, Renderer).
  130expand_rendering(Renderer, Options,
  131		 [ (:- discontiguous('swish renderer'/2)),
  132		   'swish renderer'(Renderer, Options)
  133		 ]) :-
  134	must_be(atom, Renderer).
  135
  136%%	pengines_io:binding_term(+Term, +Vars, +Options) is semidet.
  137%
  138%	Produce alternative renderings for Term, which  is a binding for
  139%	Vars.
  140
  141:- multifile pengines_io:binding_term//3.  142
  143pengines_io:binding_term(Term, Vars, Options) -->
  144	{ option(module(Module), Options),
  145	  findall(Tokens,
  146		  call_term_rendering(Module, Term, Vars, Options, Tokens),
  147		  NestedTokens),
  148	  NestedTokens \== [], !
  149	},
  150	alt_renderer(NestedTokens, Term, Options).
  151
  152%%	call_term_rendering(+Module, +Term, +Vars, +Options, -Tokens) is nondet.
  153%
  154%	Call  term_rendering//3  in  all  modules    from  which  Module
  155%	inherits.
  156
  157call_term_rendering(Module, Term, Vars, Options, Tokens) :-
  158	State = state([]),
  159	default_module(Module, Target),
  160	current_predicate(Target:'swish renderer'/2),
  161	Target:'swish renderer'(Name, RenderOptions),
  162	atom(Name),
  163	is_new(State, Name),
  164	renderer(Name, RenderModule, _Comment),
  165	merge_options(RenderOptions, Options, AllOptions),
  166	catch(phrase(RenderModule:term_rendering(Term, Vars, AllOptions), Tokens),
  167	      E, rendering_error(E, Name, Tokens)).
  168
  169rendering_error(Error, Renderer, Tokens) :-
  170	message_to_string(Error, Msg),
  171	phrase(html(div(class('render-error'),
  172			[ 'Renderer ', span(Renderer),
  173			  ' error: ', span(class(error), Msg)
  174			])), Tokens).
  175
  176
  177%%	is_new(!State, +M) is semidet.
  178%
  179%	Only succeeds once for each new ground value M.
  180
  181is_new(State, M) :-
  182	arg(1, State, Seen),
  183	(   memberchk(M, Seen)
  184	->  fail
  185	;   nb_linkarg(1, State, [M|Seen])
  186	).
  187
  188%%	alt_renderer(+Specialised, +Term, +Options)//
  189%
  190%	Create a rendering selection object after we have found at least
  191%	one alternative rendering for Term.
  192
  193alt_renderer(Specialised, Term, Options) -->
  194	html(div(class('render-multi'),
  195		 \specialised(Specialised, Term, Options))).
  196
  197specialised([], Term, Options) -->
  198	html(span([ class('render-as-prolog'),
  199		    'data-render'('Prolog term')
  200		  ],
  201		  \term(Term, Options))).
  202specialised([H|T], Term, Options) -->
  203	tokens(H),
  204	specialised(T, Term, Options).
  205
  206tokens([]) --> [].
  207tokens([H|T]) --> [H], tokens(T).
  208
  209
  210		 /*******************************
  211		 *	   REGISTRATION		*
  212		 *******************************/
  213
  214:- multifile
  215	renderer/3.  216
  217%%	current_renderer(Name, Comment) is nondet.
  218%
  219%	True when renderer Name is declared with Comment.
  220
  221current_renderer(Name, Comment) :-
  222	renderer(Name, _Module, Comment).
  223
  224%%	register_renderer(:Name, +Comment)
  225%
  226%	Register a module as SWISH rendering component.
  227
  228register_renderer(Name, Comment) :-
  229	throw(error(context_error(nodirective, register_renderer(Name, Comment)),
  230		    _)).
  231
  232system:term_expansion((:- register_renderer(Name, Comment)),
  233		    swish_render:renderer(Name, Module, Comment)) :-
  234	prolog_load_context(module, Module)